package io.kimmking.rpcfx.client;


import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.parser.ParserConfig;
import io.kimmking.rpcfx.api.*;
import io.netty.bootstrap.Bootstrap;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.handler.codec.http.HttpClientCodec;
import io.netty.handler.codec.http.HttpContentDecompressor;
import io.netty.handler.codec.http.HttpObjectAggregator;
import io.netty.handler.codec.serialization.ClassResolvers;
import io.netty.handler.codec.serialization.ObjectDecoder;
import io.netty.handler.codec.serialization.ObjectEncoder;
import net.bytebuddy.ByteBuddy;
import net.bytebuddy.dynamic.loading.ClassLoadingStrategy;
import net.bytebuddy.implementation.InvocationHandlerAdapter;
import net.bytebuddy.implementation.MethodDelegation;
import net.bytebuddy.matcher.ElementMatchers;
import okhttp3.MediaType;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.RequestBody;

import java.io.IOException;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

public final class Rpcfx {

    protected static final Map<Class<?>, Object> INSTANCW_MAP = new ConcurrentHashMap<>();

    protected static final Map<Class<?>, String> URL_MAP = new ConcurrentHashMap<>();

    private static final Map<ChannelId, RpcfxResponse> CHANNEL_RESP_MAP = new ConcurrentHashMap<>();

    private static final NioEventLoopGroup WORK_GROUP = new NioEventLoopGroup(4);

    private static final Bootstrap BOSS_GROUP = new Bootstrap();



    static {
        ParserConfig.getGlobalInstance().addAccept("io.kimmking");
    }

    public static <T> T create(final Class<T> serviceClass, final String url, Filter... filters) {
        URL_MAP.putIfAbsent(serviceClass, url);
        T proxyObj = (T) INSTANCW_MAP.get(serviceClass);
        if (proxyObj == null) {
            try {
                proxyObj = new ByteBuddy().subclass(serviceClass)
                        .method(ElementMatchers.anyOf(serviceClass.getMethods()))
                        //.intercept(MethodDelegation.to(new RpcAdvice()))
                        .intercept(InvocationHandlerAdapter.of(new RpcfxInvocationHandler(serviceClass, url, filters)))
                        .make()
                        .load(serviceClass.getClassLoader(), ClassLoadingStrategy.Default.WRAPPER)
                        .getLoaded()
                        .getDeclaredConstructor()
                        .newInstance();
            } catch (InstantiationException | IllegalAccessException | NoSuchMethodException | InvocationTargetException e) {
                e.printStackTrace();
            }
            INSTANCW_MAP.putIfAbsent(serviceClass, proxyObj);
        }

        return proxyObj;

        // 0. 替换动态代理 -> 字节码生成
        //return (T) Proxy.newProxyInstance(Rpcfx.class.getClassLoader(), new Class[]{serviceClass}, new RpcfxInvocationHandler(serviceClass, url, filters));

    }

    public static class RpcfxInvocationHandler implements InvocationHandler {

        public static final MediaType JSONTYPE = MediaType.get("application/json; charset=utf-8");


        private final Class<?> serviceClass;
        private final String url;
        private final Filter[] filters;

        public <T> RpcfxInvocationHandler(Class<T> serviceClass, String url, Filter... filters) {
            this.serviceClass = serviceClass;
            this.url = url;
            this.filters = filters;
        }

        // 可以尝试，自己去写对象序列化，二进制还是文本的，，，rpcfx是xml自定义序列化、反序列化，json: code.google.com/p/rpcfx
        // int byte char float double long bool
        // [], data class

        @Override
        public Object invoke(Object proxy, Method method, Object[] params) throws Throwable {

            // 加filter地方之二
            // mock == true, new Student("hubao");

            RpcfxRequest request = new RpcfxRequest();
            request.setServiceClass(this.serviceClass.getName());
            request.setMethod(method.getName());
            request.setParams(params);

            if (null!=filters) {
                for (Filter filter : filters) {
                    if (!filter.filter(request)) {
                        return null;
                    }
                }
            }

            RpcfxResponse response = post(request, url);
            return JSON.parse(response.getResult().toString());
        }

        private RpcfxResponse post(RpcfxRequest req, String url) throws IOException, InterruptedException {
            String reqJson = JSON.toJSONString(req);

            final String handleUrl = url.endsWith("/") ? url.substring(0, url.length() - 1) : url;
            String[] splitUrl = handleUrl.split("/");
            String[] hostAndPort = splitUrl[splitUrl.length - 1].split(":");

            // 1.可以复用client
            // 2.尝试使用httpclient或者netty client

            EventLoopGroup eventLoopGroup = new NioEventLoopGroup();
            Bootstrap bootstrap = new Bootstrap();
            try {
                bootstrap.group(eventLoopGroup)
                        .option(ChannelOption.TCP_NODELAY, true)
                        .channel(NioSocketChannel.class)
                        .handler(new ChannelInitializer<SocketChannel>() {
                            @Override
                            protected void initChannel(SocketChannel ch) throws Exception {
                                ChannelPipeline channelPipeline = ch.pipeline();
                                channelPipeline.addLast(new HttpClientCodec());
                                channelPipeline.addLast(new HttpObjectAggregator(65536));
                                channelPipeline.addLast(new HttpContentDecompressor());
                                channelPipeline.addLast(new RpcNettyHandler(reqJson, CHANNEL_RESP_MAP));
                            }
                        });
                ChannelFuture channelFuture = bootstrap.connect(hostAndPort[0], Integer.valueOf(hostAndPort[1])).sync();
                ChannelId channelId = channelFuture.channel().id();
                channelFuture.channel().closeFuture().sync();
                RpcfxResponse response = CHANNEL_RESP_MAP.get(channelId);
                CHANNEL_RESP_MAP.remove(channelId);
                return response;
            } finally {
                eventLoopGroup.shutdownGracefully();
            }
        }
    }
}
